package server;

import client.MapleCharacter;
import client.skills.SkillFactory;
import constants.ServerConstants;
import database.DatabaseConnection;
import handling.world.CharacterTransfer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import provider.MapleOverrideData;
import redis.clients.jedis.Jedis;
import server.cashshop.CashItemFactory;
import server.console.Start;
import server.life.MapleLifeFactory;
import server.life.MapleMonsterInformationProvider;
import server.life.MobSkillFactory;
import server.maps.MapleMapFactory;
import server.quest.MapleQuest;
import server.shop.MapleShopFactory;
import server.skill.MapleForceAtomFactory;
import tools.JsonUtil;
import tools.Pair;
import tools.RedisUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 初始化服务器配置，比如更新数据库格式等
 */
public class InitializeServer {

    private static final Logger log = LogManager.getLogger(InitializeServer.class.getName());

    /**
     * 服务端初始化，应用数据库更改等
     *
     * @return
     */
    public static boolean Initial() {
        initializeSetting();
        return initializeUpdateLog() && initializeMySQL(); //
    }

    /**
     * 初始化设置
     * 1、账号状态
     */
    private static void initializeSetting() {
        try (Connection con = DatabaseConnection.getConnection()) {
            try (PreparedStatement ps = con.prepareStatement("UPDATE `accounts` SET `loggedin` = 0, `check` = 0")) {
                ps.executeUpdate();
            }
        } catch (SQLException ex) {
            throw new RuntimeException("[EXCEPTION] Please check if the SQL server is active.");
        }
    }

    /**
     * 创建更新日志的记录表
     *
     * @return
     */
    private static boolean initializeUpdateLog() {
        if (!checkTableisExist()) {
            try (Connection con = DatabaseConnection.getConnection()) {
                try (PreparedStatement ps = con.prepareStatement("CREATE TABLE `systemupdatelog` (`id`  INT(11) NOT NULL AUTO_INCREMENT,`patchid`  TINYINT(1) NOT NULL ,`lasttime`  TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP ,PRIMARY KEY (`id`))")) {
                    ps.executeUpdate();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return checkTableisExist();
    }

    /**
     * 查看更新日志记录表是否存在
     *
     * @return
     */
    private static boolean checkTableisExist() {
        boolean exist = false;
        try (Connection con = DatabaseConnection.getConnection()) {
            try (PreparedStatement ps = con.prepareStatement("SHOW TABLES LIKE 'systemupdatelog'"); ResultSet rs = ps.executeQuery()) {
                if (rs.next()) {
                    exist = true;
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return exist;
    }

    /**
     * 应用MySQL补丁
     *
     * @return
     */
    private static boolean initializeMySQL() {
        // 检查并应用MySQL补丁
        for (UPDATE_PATCH patch : UPDATE_PATCH.values()) {
            if (!patch.getSQL().isEmpty() && !checkIsAppliedSQLPatch(patch.ordinal())) {
                if (!applySQLPatch(patch.getSQL()) || !insertUpdateLog(patch.ordinal())) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 检查是否已应用补丁
     *
     * @param id
     * @return TRUE: 已应用    FALSE: 未应用
     */
    private static boolean checkIsAppliedSQLPatch(int id) {
        try (Connection con = DatabaseConnection.getConnection()) {
            try (PreparedStatement ps = con.prepareStatement("SELECT id FROM systemupdatelog WHERE patchid = ?")) {
                ps.setInt(1, id);
                try (ResultSet rs = ps.executeQuery()) {
                    if (rs.next()) {
                        return true;
                    }
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 插入补丁的更新记录
     *
     * @param patchid 补丁id
     * @return
     */
    private static boolean insertUpdateLog(int patchid) {
        try (Connection con = DatabaseConnection.getConnection()) {
            try (PreparedStatement ps = con.prepareStatement("INSERT INTO systemupdatelog(id, patchid, lasttime) VALUES (DEFAULT, ?, CURRENT_TIMESTAMP)")) {
                ps.setInt(1, patchid);
                ps.executeUpdate();
                return true;
            }
        } catch (SQLException ex) {
            throw new RuntimeException(ex.getMessage());
        }
    }

    private static boolean applySQLPatch(String sql) {
        try (Connection con = DatabaseConnection.getConnection()) {
            try (PreparedStatement ps = con.prepareStatement(sql)) {
                ps.executeUpdate();
                return true;
            }
        } catch (SQLException e) {
            if (e.getMessage().contains("Duplicate column name") || e.getMessage().contains("Cannot add foreign key constraint") || e.getMessage().contains("already exists")) {
                return true;
            }
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 关闭c3p0日志输出
     */
//    public static void closeC3P0Log() {
//        Properties p = new Properties(System.getProperties());
//        p.put("com.mchange.v2.log.MLog", "com.mchange.v2.log.FallbackMLog");
//        p.put("com.mchange.v2.log.FallbackMLog.DEFAULT_CUTOFF_LEVEL", "OFF"); // or any other
//        System.setProperties(p);
//    }
    public static boolean initializeRedis(boolean reload, Start.ProgressBarObservable observable) {
        Jedis jedis = RedisUtil.getJedis();
        if (RedisUtil.KEYNAMES.DELETECACHE || reload) {
            jedis.flushAll();
        }
        RedisUtil.returnResource(jedis);

        try {
            List<Runnable> runnables = new ArrayList<>();
            runnables.add(() -> {
                // 加载技能冷却时间
                SkillFactory.loadDelays();
                // 加载技能重载数据
                MapleOverrideData.getInstance().init();
                // 加载技能数据
                SkillFactory.loadSkillData();
                // 加载复制技能
                SkillFactory.loadMemorySkills();
            });
            runnables.add(MapleItemInformationProvider.getInstance()::loadItemNameDesc);
            runnables.add(MapleItemInformationProvider.getInstance()::loadHairAndFace);
            runnables.add(MapleItemInformationProvider.getInstance()::loadFamiliarOption);
            runnables.add(() -> {
                // 加载道具数据
                MapleItemInformationProvider.getInstance().loadItemData();
                // 加载怪物爆率
                MapleMonsterInformationProvider.getInstance().load();
                // 加载商店数据
                MapleShopFactory.getInstance().loadShopData();
            });
            // 加载套装数据
            runnables.add(MapleItemInformationProvider.getInstance()::loadSetItemData);
            // 加载潜能数据
            runnables.add(MapleItemInformationProvider.getInstance()::loadPotentialData);
            // 加载星岩数据
            runnables.add(() -> MapleItemInformationProvider.getInstance().loadSocketData());
            // 加载地图信息
            runnables.add(MapleMapFactory::loadAllLinkNpc);
            // 加载怪物名称
            runnables.add(MapleLifeFactory::loadMonsterName);
            // 加载怪物ID
            runnables.add(MapleLifeFactory::loadMonsterID);
            // 加载NPC名称与任务数量
            runnables.add(MapleLifeFactory::loadQuestCounts);
            // 加载地图名称
            runnables.add(MapleMapFactory::loadAllMapName);
            // 加载怪物技能信息
            runnables.add(MobSkillFactory::initialize);
            // 加载商城道具数据
            runnables.add(CashItemFactory.getInstance()::initialize);
            // 加载任务数据
            runnables.add(MapleQuest::initQuests);
            // 加载forceAtom数据
            runnables.add(MapleForceAtomFactory.INSTANCE::initialize);
            // 将数据库内所有角色数据保存到Redis
            runnables.add(InitializeServer::saveAllPlayerCache);
            // 初始化服务端的FLAG
            runnables.add(ServerConstants::initServerFlag);


            int currPro = observable.getProgress();
            int singlePro = (100 - currPro) / (runnables.size() + 1);
            ForkJoinPool pool = new ForkJoinPool();
            pool.invoke(new RecursiveAction() {

                private AtomicInteger count = new AtomicInteger(1);

                @Override
                protected void compute() {
                    List<RecursiveAction> task = runnables.parallelStream().map(runnable -> new RecursiveAction() {
                        @Override
                        protected void compute() {
                            runnable.run();
                            observable.setProgress(new Pair<>("正在载入数据... [" + count + "/" + runnables.size() + "]", currPro + singlePro * count.get()));
                            count.incrementAndGet();
                        }
                    }).collect(Collectors.toList());
                    invokeAll(task);
                }
            });
            pool.shutdown();
//            initializeMySQL();
            observable.setProgress(new Pair<>("服务端初始化完成，正在启动主界面...", 100));
        } catch (Exception e) {
            log.error("服务端初始化失败", e);
            Start.showMessage("服务端初始化失败", "错误", 0);
//            RedisUtil.flushall();
            System.exit(0);
        }

        return true;
    }

    private static void saveAllPlayerCache() {
        saveAllPlayerCache(true);
    }

    private static void saveAllPlayerCache(boolean delete) {
        Jedis jedis = RedisUtil.getJedis();
        if (jedis.exists(RedisUtil.KEYNAMES.PLAYER_DATA.getKeyName())) {
            try {
                Map<String, String> datas = jedis.hgetAll(RedisUtil.KEYNAMES.PLAYER_DATA.getKeyName());
                for (Map.Entry<String, String> entry : datas.entrySet()) {
                    CharacterTransfer ct = JsonUtil.getMapperInstance().readValue(entry.getValue(), CharacterTransfer.class);
                    MapleCharacter.ReconstructChr(ct, null, false).saveToDB(true, false, false);
                }
            } catch (Exception e) {
                log.error("初始化玩家缓存出错", e);
                Start.showMessage("初始化玩家缓存出错,请联系我们进行解决", "警告", 0);
                System.exit(0);
            } finally {
                if (delete) {
                    jedis.del(RedisUtil.KEYNAMES.PLAYER_DATA.getKeyName());
                }
            }
        }
        RedisUtil.returnResource(jedis);
    }

    /**
     * 补丁列表
     */
    enum UPDATE_PATCH {

        ANDROID增加外键,
        添加怪怪是否召唤的字段,
        幻影偷取终极斩,
        添加客户端keyValue,
        清除冒险岛联盟任务("DELETE FROM `queststatus` WHERE (`quest`='16014')"),
        修改5转技能表("ALTER TABLE `vcoreskill`ADD COLUMN `dateexpire` bigint(20) NULL DEFAULT -1 AFTER `skill3`,ADD COLUMN `index`  int(11) NULL DEFAULT -1 AFTER `slot`"),
        添加vmatrixslot("CREATE TABLE `vmatrixslot` (`characters_id` int(11) NOT NULL,`slot` int(11) NOT NULL,`extend` int(11) NOT NULL DEFAULT '0',`unlock` int(11) NOT NULL DEFAULT '0',PRIMARY KEY (`characters_id`,`slot`),KEY `characters_id` (`characters_id`),CONSTRAINT `vmatrixslot_ibfk_1` FOREIGN KEY (`characters_id`) REFERENCES `characters` (`id`) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=gbk");

        private final String sql;

        UPDATE_PATCH() {
            sql = "";
        }

        UPDATE_PATCH(String sql) {
            this.sql = sql;
        }

        public String getSQL() {
            return sql;
        }
    }
}
